웹팩 ( Webpack + TypeScript + React )
포스트
취소

웹팩 ( Webpack + TypeScript + React )

해당 포스트는 webpack + TypeScript + React를 세팅하는 방법에 대한 포스트입니다.
대부분의 내용은 ryuhojin님의 포스트를 참고했으며 이해했고 필요한 부분만 추가했고 모르는 부분은 제외했습니다.

결과물은 GitHub - boilerplate에 올렸습니다.

📲 Webpack 이란

webpack은 모던 JavaScript를 위한 정적 모듈 번들러입니다.
자체적으로는 JavaScriptJSON만 해석이 가능하고 다른 것을 해석하기 위해서는 그에 맞는 loader가 필요합니다.

0️⃣ 번들링/번들러

번들러란 여러 파일들을 번들링 해주는 도구입니다.
번들링란 웹 애플리케이션을 구성하는 여러 개의 자원들을 하나로 병합 및 압축해주는 동작을 의미합니다.
( 브라우저는 HTML, CSS, JavaScript 밖에 해석하지 못합니다. ( 이미지, 폰트 등의 에셋은 제외 ) )

  • scss, sass, css => css
  • a.jsx, b.js => js
  • a.png, b.png => z.png

하지만 무조건적으로 하나로 합치지는 않습니다.
상황과 조건에 따라서 파일로 나누고 필요한 부분만 나중에 불러오는 코드 스플릿팅같은 기능도 있습니다.

1️⃣ 번들러 사용 이유

  1. 중복된 이름으로 인한 문제를 해결하기 위함
  2. 파일 전송의 효율성을 위함

여러 파일을 합칠 때 발생할 수 있는 중복된 이름 문제는 모듈화로 해결합니다.
또한 HTTP, HTTPS에서는 크기가 100인 파일 1개를 전송하는 것이 크기가 10인 파일 10개를 전송하는 것보다 빠르기 때문에 파일을 하나로 합치면 네트워크 비용에서 더 이점이 있습니다.

2️⃣ 번들러 기능

  1. 모든 종류의 파일을 모듈 단위로 나눠서 최소한의 묶음으로 만듦
  2. 난독화 및 압축
  3. 문법 변환 ( 트랜스파일 )

진입점을 기준으로 연관된 모든 파일들을 대부분 하나로 합쳐서 난독화 및 압축 및 문법 변환을 해줍니다.
따라서 외부에서 파일을 읽기가 힘들고, 파일이 더 가벼워지고, 구버전 브라우저 동작 문제나 크로스 브라우징 문제도 해결할 수 있습니다.

3️⃣ Webpack을 사용하는 이유

  1. 많은 플러그인과 로더
  2. 안정적이고 많은 레퍼런스 존재
  3. Hot Module Replacement ( Dev Server )

Dev Server는 메모리에만 존재하는 번들링된 데이터입니다.
( 즉, 파일을 직접 만들지 않음 ( 컴퓨터 자원을 덜 사용 ) )

4️⃣ Webpack의 핵심 컨셉 및 기본 설정 방법

Webpack의 설정 값들은 전부 이해하고 사용할 수 없을 정도로 너무 많기 때문에 모두 이해하기 보다는 일단 Webpack - 공식 문서를 먼저 가볍게 읽어보고 흐름을 살펴본 뒤에 이후에 필요한 기능이 있을 때마다 찾아가면서 적용하는 것이 좋아보입니다.

아래에 작성한 것은 기본적인 설정 방법입니다.
필요 없는 것이 있다면 제거하고 필요한 것이 있다면 설치하고 추가하면 됩니다.

  • 핵심 컨셉 네 가지
    1. entry: 번들링의 진입점 ( 진입점을 기준으로 연관된 파일들을 번들링 )
    2. output: 번들링된 결과물에 대한 설정
    3. module: 번들링하는데 사용하는 loader 설정 ( js, JSON외 다른 것을 번들링할 때 사용 )
    4. plugin: 번들링한 결과물에 특정 처리를 할 때 사용할 plugin 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import path from "path";
import webpack from "webpack";

// plugin
import HtmlWebpackPlugin from "html-webpack-plugin";

const configuration: webpack.Configuration = {
  mode: "",

  // 모듈 해석 방법 설정
  resolve: {
    // 생략할 확장자
    extensions: [".ts", ".tsx", ".js", ".jsx"],

    // 절대 경로
    alias: {
      "@src": path.resolve(__dirname, "../src/"),
    },
  },

  // 진입점
  entry: "./src/index",

  // 결과물
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[name].bundle.js",
  },

  // 로더
  module: {
    // 로더들 명시할 배열
    rules: [
      // babel-loader
      {
        test: /\.(ts|tsx|js|jsx)$/,
        use: ["babel-loader"],
        exclude: /node_modules/,
      },
      // 스타일 관련 로더 ( 우측부터 실행 )
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
        exclude: /node_modules/,
      },
    ],
  },

  // 플러그인
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "..", "public", "index.html"),
    }),
  ],
};

export default configuration;

📄 설치

0️⃣ React

1
npm i react react-dom react-router-dom
  • react: React를 사용하기 위해 설치
  • react-dom: React의 데이터를 실제 DOM으로 변경하기 위해 사용
  • react-router-dom: SPA의 라우팅 처리를 위해 사용

1️⃣ Babel

babel에 대해 궁금하시다면 포스트를 읽어보시는 것을 추천드립니다.

1
npm i -D @babel/preset-env @babel/preset-typescript @babel/preset-react core-js

preset이란 자주 사용하는 babel 플러그인들을 합쳐서 제공하는 것을 의미합니다.

2️⃣ Webpack

1
npm i -D webpack webpack-cli webpack-dev-server webpack-merge
  • webpack: webpack을 사용하기 위해 설치
  • webpack-cli: CLI에서 webpack 명령어를 사용하기 위해 설치
  • webpack-dev-server: 개발 서버로 실행하기 위해 설치
  • webpack-merge: 모드에 따라 다른 설정을 적용한 파일을 합치는데 사용

3️⃣ Loader

1
npm i -D css-loader style-loader babel-loader
  • css-loader: css파일을 읽는 loader
  • style-loader: cssinline으로 <style></style>에 넣어주는 loader
  • babel-loader: webpack에서 babel을 사용할 수 있게 도와주는 loader

4️⃣ Plugin

1
2
3
4
npm i -D html-webpack-plugin css-minimizer-webpack-plugin mini-css-extract-plugin webpack-bundle-analyzer

# "React"를 사용하고 "HMR"이 필요하다면 설치
npm i @pmmmwh/react-refresh-webpack-plugin

5️⃣ TypeScript

1
npm i -D typescript ts-node @types/react @types/react-dom @types/webpack @types/webpack-dev-server @types/node
  • typescript: TypeScript 사용을 위해 설치
  • ts-node: Node.js에서 TypeScript 실행을 위해 설치
  • @types/react: React의 타입 선언
  • @types/react-dom: ReactDOM의 타입 선언
  • @types/webpack: Webpack의 타입 선언
  • @types/webpack-dev-server: Webpack Dev Server의 타입 선언
  • @types/node: Node의 타입 선언

🧹 세팅

0️⃣ package.json 세팅

개발/배포용 실행과 빌드를 위해서 명령어를 추가해줘야합니다.
webpack 설정 파일을 개발/배포용으로 나눴을 경우의 명령어이고 따로 나누지 않았다면 webpack.dev.ts 부분을 해당 파일 이름으로 바꿔주면 됩니다.

1
2
3
4
5
6
7
8
{
  // ... 나머지 생략
  "scripts": {
    "dev": "npx webpack serve --config config/webpack.dev.ts",
    "build:dev": "npx webpack --config config/webpack.dev.ts",
    "build:prod": "npx webpack --config config/webpack.prod.ts"
  },
}

1️⃣ TypeScript 세팅 ( tsconfig.json )

(1)은 절대 경로를 위한 설정 값입니다. ( @src/components, @src/utils 등 )
(2)webpack 설정 파일에서 ES6 모듈 시스템을 사용하기 위한 설정입니다. ( 즉, 설정 파일에 TypeScript를 사용하기 위함 )
( webpack - TypeScript 참고 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
  "compilerOptions": {
    "outDir": "./dist",
    "target": "ES5",
    "module": "ESNext",
    "jsx": "react-jsx",
    "noImplicitAny": true,
    "allowSyntheticDefaultImports": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,

    // (1) 절대 경로
    "baseUrl": ".",
    "paths": {
      "@src/*": ["src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"],

  // (2) "webpack"설정 파일들을 "typescript" 처리하기 위해 사용
  "ts-node": {
    "compilerOptions": {
      "module": "CommonJS"
    }
  }
}

2️⃣ Babel 세팅 ( babel.config.js )

사용할 babelpreset을 등록하는 설정 파일입니다.

  1. modules: false: ES6 모듈 시스템 사용을 위해 적용 ( Tree Shaking )
  2. useBuiltIns: "usage": corejs에서 사용하는 polyfillimport로 삽입
  3. corejs: 3: 사용할 corejs 버전 명시 ( 기본 값 2 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const presets = [
  "@babel/preset-react",
  [
    "@babel/preset-env",
    {
      modules: false, // "Tree Shaking"을 위함 ( ES6 모듈 시스템 사용 )
      useBuiltIns: "usage", // "corejs"에서 사용하는 "polyfill"만 "import"만 삽입
      corejs: 3, // 사용할 `corejs` 버전 명시
    },
  ],
  "@babel/preset-typescript",
];
const plugins = [];

module.exports = { presets, plugins };

3️⃣ Webpack 세팅 ( 공통 ) ( webpack.common.ts )

공통 설정 파일에 작성한 내용은 개발 / 배포 설정 파일에 모두 적용됩니다.( webpack-merge )

webpack은 총 3개의 파일로 나눠서 세팅합니다.
config 폴더 내부에 세 개의 파일을 작성합니다.
( 어차피 package.json에 사용할 세팅 파일을 명시할 것이기 때문에 폴더명은 상관 없습니다. )

굳이 파일을 나누기 싫다면 webpack-merge를 사용하지 않고 webpack.config.ts 하나로 통일해도 됩니다.

  1. 공통 설정 파일 ( config/webpack.common.ts )
  2. 개발용 설정 파일 ( config/webpack.dev.ts )
  3. 배포용 설정 파일 ( config/webpack.prod.ts )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// webpack.common.ts

import path from "path";
import webpack from "webpack";

// plugin
import HtmlWebpackPlugin from "html-webpack-plugin";

const configuration: webpack.Configuration = {
  // 모듈 해석 방법 설정
  resolve: {
    // 생략할 확장자
    extensions: [".ts", ".tsx", ".js", ".jsx"],

    // 절대 경로
    alias: {
      "@src": path.resolve(__dirname, "../src/"),
    },
  },

  // 진입점
  entry: "./src/index",

  // 로더
  module: {
    rules: [
      // babel-loader ( react, ts, polyfill, 구문 변환 등을 처리 ( babel.config.js ) )
      {
        test: /\.(ts|tsx|js|jsx)$/,
        use: ["babel-loader"],
        exclude: /node_modules/,
      },
    ],
  },

  // 플러그인
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "..", "public", "index.html"),
    }),
    // import React from "react" 생략을 위한 플러그인 설정
    new webpack.ProvidePlugin({ React: "react" }),
  ],
};

export default configuration;

4️⃣ Webpack 세팅 ( 개발용 ) ( webpack.dev.ts )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// webpack.dev.ts

import path from "path";
import webpack from "webpack";
import "webpack-dev-server";
import { merge } from "webpack-merge";
import common from "./webpack.common";

// plugin
import RefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";

const configuration: webpack.Configuration = {
  mode: "development",
  devtool: "inline-source-map",
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[name].bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [new RefreshWebpackPlugin()],
  devServer: {
    static: path.join(__dirname, "public"),
    port: 3000,
    open: true,
    compress: true, // 모든 항목을 `gzip`으로 압축
    historyApiFallback: true, // 존재하지 않는 "URL" 접근 시 "index.html"로 대체
    hot: true // "Hot Mourle Replacement" 사용
  },
  watchOptions: {
    ignored: /node_modules/, // 감시하지 않을 항목 설정
  },
};

export default merge(common, configuration);
  • devServer
    1. static: 정적 파일의 제공
    2. static.publicPath: 정적 파일 접근 URL 설정
    3. allowedHosts: 개발 서버에 접근할 수 있는 호스트들 작성 ( all도 가능 )
    4. compress: 모든 항목을 gzip으로 압축함
    5. headers: 모든 응답 헤더에 추가 ( 객체 or 배열 )
    6. historyApiFallback: 존재하지 않는 URL접근 시 404 대신 index.html 제공
    7. onAfterSetupMiddleware, onBeforeSetupMiddleware: 커스텀 미들웨어
    8. open: 서버 실행 후 브라우저 열기
    9. port: 포트 결정 ( auto 가능 )
    10. proxy: 프록시 서버 설정

5️⃣ Webpack 세팅 ( 배포용 ) ( webpack.prod.ts )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// webpack.prod.ts

import path from "path";
import webpack from "webpack";
import { merge } from "webpack-merge";
import common from "./webpack.common";

// plugin
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";

const configuration: webpack.Configuration = {
  mode: "production",
  devtool: "cheap-module-source-map",
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[name].[contenthash].js",
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
  optimization: {
    usedExports: true, // 사용하지 않는 "export" 제거
    minimize: true, // "minimizer"에 지정한 플러그인을 사용하여 번들 최소화
    minimizer: [new CssMinimizerPlugin()], // 최적화에 사용할 플러그인 적용
  },
};

export default merge(common, configuration);

🃏 알쓸개잡

Eslint & Prettier 적용

EslintPrettier 적용 방법이 궁금하면 Eslint & Prettier ( + TypeScript )를 참고해주세요!

0️⃣ tailwindCss 적용 방법

tailwindCss의 모든 내용은 poiemaweb - tailwind를 참고해서 만들었습니다.
완성본은 GitHub에 업로드했습니다.

1. 설치

1
2
3
4
5
# "tailwindcss"를 사용하기 위해 설치
npm i -D tailwindcss postcss autoprefixer

# 로더 설치
npm i -D postcss-loader

2. tailwind.config.js 파일 작성

1
npx tailwindcss init
1
2
3
4
5
6
7
8
9
10
// "tailwind.config.js" 파일

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{ts,tsx,js,jsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

3. postcss.config.js 파일 작성

1
2
3
4
5
6
7
8
// "postcss.config.js" 파일

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

4. css 파일 생성 및 추가

1
2
3
4
5
/* "tailwind.css" 파일 ( 파일명 무관 ) */

@tailwind base;
@tailwind components;
@tailwind utilities;

위 파일을 생성하고 진입점(index.tsx)에 추가합니다.
굳이 진입점이 아니어도 상관 없지만, 저는 주로 진입점에서 css파일을 추가하는 편입니다.

1
2
3
4
5
6
7
8
9
10
11
12
import { createRoot } from "react-dom/client";

// css
import "@src/style/tailwind.css";

// component
import App from "@src/components/App";

const container = document.getElementById("root");
const root = createRoot(container!);

root.render(<App />);

5. webpack.config.ts 수정

  • webpack.dev.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ... 나머지 생략

const configuration: webpack.Configuration = {
  // ... 나머지 생략
  module: {
    rules: [
      {
        test: /\.css$/,
        // "postcss-loader" 추가
        use: ["style-loader", "css-loader", "postcss-loader"],
        exclude: /node_modules/,
      },
    ],
  },
  // ... 나머지 생략
};

export default merge(common, configuration);
  • webpack.prod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ... 나머지 생략

const configuration: webpack.Configuration = {
  // ... 나머지 생략
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
    ],
  },
  // ... 나머지 생략
};

export default merge(common, configuration);

📮 레퍼런스

  1. 1-blue - GitHub - boilerplate(webpack)
  2. 1-blue - GitHub - boilerplate(webpack + tailwindCss)

  3. webpack
  4. ryuhojin - React + TypeScript + Webpack5 초기 설정
  5. tech.osci.kr - CRA없이 배우는 Webpack

  6. 1-blue - babel
  7. babel - @babel/preset-env
  8. babel - @babel/preset-react
  9. babel - @babel/preset-typescript

  10. webpack - webpack
  11. webpack - webpack-cli
  12. webpack - webpack-dev-server
  13. webpack - webpack-merge

  14. npm - css-loader
  15. npm - style-loader
  16. npm - babel-loader

  17. webpack - html-webpack-plugin
  18. npm - css-minimizer-webpack-plugin
  19. npm - mini-css-extract-plugin
  20. npm - webpack-bundle-analyzer

  21. webpack - TypeScript
  22. 1-blue - Tree Shaking

  23. poiemaweb - tailwind
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.